package dkbta.ontology.elements;

import static dkbta.util.time.TimeUtils.timeAndDate;

import java.sql.PreparedStatement;
import java.sql.SQLException;

import dkbta.ontology.values.ValuesType;
import dkbta.util.time.TimeInterval;

/**
 * An element instance
 * 
 * @author Uri Kanonov
 */
public abstract class Element implements Comparable<Element>{
	/** A stored hashcode for faster hashing later on */
	private final int _hashCode;

	/** The element's type */
	protected final ElementType _type;

	/** The element's name */
	private final String _name;

	/** The time interval [start, end] */
	protected final TimeInterval _timeInterval;

	/** The value of the element (numeric or symbolic) */
	protected Object _value;

	/** The type of the value symbolic (String) or numeric (Double) */
	private final ValuesType _valueType;

	/**
	 * Initializes an element instance with the given parameters
	 * 
	 * @param elementType The element's type
	 * @param name The element's name
	 * @param timeInterval The time interval
	 * @param value The value of the element (numeric or symbolic)
	 * @param valueType The type of the value symbolic (String) or numeric (Double)
	 */
	public Element(ElementType elementType, String name, TimeInterval timeInterval,
		Object value, ValuesType valueType){
		_type = elementType;
		_name = name.intern();
		_timeInterval = timeInterval;
		_value = value;
		_valueType = valueType;
		_hashCode = (getElementType().ordinal() + getName()).hashCode();
	}

	/**
	 * Initializes an element instance with the given parameters
	 * 
	 * @param elementType The element's type
	 * @param name The element's name
	 * @param startTime The element's start time
	 * @param endTime The element's end time
	 * @param value The value of the element (numeric or symbolic)
	 * @param valueType The type of the value symbolic (String) or numeric (Double)
	 */
	public Element(ElementType elementType, String name, long startTime, long endTime,
		Object value, ValuesType valueType){
		this(elementType, name, new TimeInterval(startTime, endTime), value, valueType);
	}

	/**
	 * Returns the value of the element, if numeric it's a Double, if symbolic it's a
	 * String
	 * 
	 * @return The value of the element, if numeric it's a Double, if symbolic it's a
	 *         String
	 */
	public Object getValue(){
		return _value;
	}

	/**
	 * Returns the time interval
	 * 
	 * @return The time interval
	 */
	public final TimeInterval getTimeInterval(){
		return _timeInterval;
	}

	/**
	 * Returns the type of the value symbolic (String) or numeric (Double)
	 * 
	 * @return The type of the value symbolic (String) or numeric (Double)
	 */
	public final ValuesType getValueType(){
		return _valueType;
	}

	/**
	 * Returns the name of the element this is the instance of
	 * 
	 * @return The name of the element this is the instance of
	 */
	public final String getName(){
		return _name;
	}

	/**
	 * Returns a string representation of the instance
	 * 
	 * @return A string representation of the instance
	 */
	@Override
	public String toString(){
		return /* _type + " - " + */_name + " " + _timeInterval
				+ ((_value == null) ? "" : " = " + _value);
	}

	/**
	 * Returns the element instance type of this instance
	 * 
	 * @return The element instance type of this instance
	 */
	public final ElementType getElementType(){
		return _type;
	}

	/**
	 * Compares two elements based on their time interval
	 * 
	 * @param o The other element to be compared to
	 * @return A negative integer, zero, or a positive integer as this interval's start
	 *         time is earlier than, equal to, or later than that of the other time
	 *         interval
	 */
	public final int compareTo(Element o){
		return _timeInterval.compareTo(o._timeInterval);
	}

	/**
	 * Decides whether this instance is equal to another object. An element is equal to
	 * other elements of the same type and name and to "typed-names", with the same type
	 * and name.
	 * 
	 * @param other The other object
	 * @return Whether this is equal to other
	 */
	@Override
	public boolean equals(Object other){
		String name = null;
		ElementType et = null;

		if (other instanceof TypedName){
			TypedName o = (TypedName)other;
			et = o.getElementType();
			name = o.getName();
		}else if (other instanceof Element){
			Element o = (Element)other;
			et = o.getElementType();
			name = o.getName();
		}
		return getName().equals(name) && getElementType().equals(et);
	}

	@Override
	public int hashCode(){
		return _hashCode;
	}

	/**
	 * Fills in the type and value of the instance (both as strings) into their respective
	 * indices in the prepared statement. This method is used when sending data to the
	 * database
	 * 
	 * @param ps The prepared statement
	 * @param valueIndex The index at which the value should be inserted
	 * @param typeIndex The index at which the type should be inserted
	 * @throws SQLException If an error occurs during the insertion
	 */
	public abstract void insertDataIntoPS(PreparedStatement ps, int valueIndex,
		int typeIndex) throws SQLException;

	/**
	 * Serializes the instance in relation to the given patient it into the given string
	 * builder. If the string builder is null, a new string builder is created and
	 * returned The time interval is serialized in millisecond form. <br>
	 * The format of the serialization is:
	 * 
	 * <pre>
	 * Pid,ElementType,ElementName,StartTime,EndTime,[Value] 
	 * </pre>
	 * 
	 * [Value] means the value is optional, depending on the element type
	 * 
	 * @param patientId The patient id
	 * @param into The string builder into which to serialize the instance (if it's null,
	 *        a new one is created and returned)
	 * @return The string builder with the serialized contents
	 */
	public StringBuilder serializeMillis(int patientId, StringBuilder into){
		StringBuilder sb = (into == null) ? new StringBuilder() : into;
		sb.append(patientId).append(",");
		sb.append(_type).append(",");
		sb.append(_name).append(",");
		sb.append(_timeInterval.getStartTime()).append(",");
		sb.append(_timeInterval.getEndTime());
		if (_value != null){
			sb.append(",").append(_value);
		}
		return sb;
	}

	/**
	 * Serializes the instance in relation to the given patient it into the given string
	 * builder. If the string builder is null, a new string builder is created and
	 * returned. The time interval is serialized using TimeUtils.timeAndDate<br>
	 * The format of the serialization is:
	 * 
	 * <pre>
	 * Pid,ElementType,ElementName,StartTime,EndTime,[Value] 
	 * </pre>
	 * 
	 * @param patientId The patient id
	 * @param into The string builder into which to serialize the instance (if it's null,
	 *        a new one is created and returned)
	 * @return The string builder with the serialized contents
	 */
	public StringBuilder serializeTimeAndDate(int patientId, StringBuilder into){
		StringBuilder sb = (into == null) ? new StringBuilder() : into;
		sb.append(patientId).append(",");
		sb.append(_type).append(",");
		sb.append(_name).append(",");
		sb.append(timeAndDate(_timeInterval.getStartTime())).append(",");
		sb.append(timeAndDate(_timeInterval.getEndTime()));
		if (_value != null){
			sb.append(",").append(_value);
		}
		return sb;
	}
}
